package application;

import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;

import jminhep.algorithms.Fuzzy;
import jminhep.cluster.DataHolder;
import jminhep.cluster.DataPoint;
import jminhep.cluster.Partition;
import jminhep.gui.SetEnv;
import jminhep.utils.VEC;

import parser.ErlangParser;
import preprocessing.AvgAggregator;
import preprocessing.Normalizer;

import utils.config.Config;
import visual.KMLGraph;
import visual.KMLPrinterCluster;

import data.CityArea;
import data.MatrixTime;

public class ErlangCluster {
	
	private Calendar START;
	private Calendar END;
	private CityArea AREA;
	private String OUT_FILE;
	private String OUT_MAP;
	private String COLORS_FILE;
	private boolean SPACE_NORM;
	private boolean TIME_NORM;
	private boolean WDMORNING = true;
	private boolean WDAFTERNOON = false;
	private boolean WEMORNING = false;
	private boolean WEAFTERNOON = false;
	
	private double PROB_LIMIT = 0.93;
	
	public static void main(String[] args)throws Exception{
		ErlangCluster ec = new ErlangCluster();
		
		double formula = 1+(1418/54+22.05)*Math.pow(48,-2)+(12.33/54+0.243)*Math.pow(48,(-0.0406*Math.log(54)-0.1134));
		System.out.println("FUZZIER = "+formula);
		ec.run();
	}
	
	public ErlangCluster(){
		
		Config config = new Config(this.getClass().getSimpleName()+".ser"); 
		
		START = (Calendar)config.get("START");
		END = (Calendar)config.get("END");
		
		
		AREA = new CityArea((String)config.get("AREA_NAME"), 
				   (double[][])config.get("AREA_BBOX"),
				   (String[])config.get("BASE_IN_DIRS"));
		
		System.out.println(AREA.getRadius());
		
		OUT_FILE = (String)config.get("OUT_FILE");
		OUT_MAP = (String)config.get("OUT_MAP");
		COLORS_FILE = (String)config.get("COLORS_FILE");
		SPACE_NORM = (Boolean)config.get("SPACE_NORM");
		TIME_NORM = (Boolean)config.get("TIME_NORM");
	}
	
	
	public void run() throws Exception{
		final int[] WEEKDAYS = {Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY};
		final int[] WEEKENDS = {Calendar.SATURDAY, Calendar.SUNDAY};
		
		
		System.out.println("Start...");
		
		List<MatrixTime> wdays = new ArrayList<MatrixTime>();
		List<MatrixTime> wends = new ArrayList<MatrixTime>();
		
		for(String dir: AREA.getBase_in_dirs()){
			parseDir(dir,wdays, START, END, AREA, WEEKDAYS);
			parseDir(dir,wends, START, END, AREA, WEEKENDS);
	    }
		
		
		System.out.println("Reading Complete...");
		
		
		//normalizations
		List<MatrixTime> day_avg_weekdays = AvgAggregator.day_avg(wdays);
		List<MatrixTime> day_avg_weekends = AvgAggregator.day_avg(wends);
		System.out.println(day_avg_weekdays.size());
		
	
		if(SPACE_NORM){
			Normalizer.space_normalize(day_avg_weekdays);
			Normalizer.space_normalize(day_avg_weekends);
		}
		if(TIME_NORM) {
			double[][] norm = computeTemporalAvg(START, END, AREA);
			Normalizer.time_normalize(day_avg_weekdays,norm);
			Normalizer.time_normalize(day_avg_weekends,norm);
		}
		
		//compute signature
		String[][] labels = computeSignature(day_avg_weekdays, day_avg_weekends);
		
		fuzzyCmeans(labels, day_avg_weekdays.get(0));		

	}
	
	public static void parseDir(String dir, List<MatrixTime> ets, Calendar START, Calendar END, CityArea AREA, int[] DAYS) throws Exception {
		File fdir = new File(dir);
		File[] files = fdir.listFiles();
		for(File f: files) {
			String[] elements = f.getName().split("_");
			Calendar start = ErlangParser.parseDateFileName(elements[3]);
			Calendar end = ErlangParser.parseDateFileName(elements[4]);
			
			
			boolean include = false;
			for(int i=0; i<DAYS.length;i++) {
				if(start.get(Calendar.DAY_OF_WEEK) == DAYS[i]) 
					include = true;
			}
			
			if(include) {
				if(!start.before(START) && !end.after(END)) {
					ets.add(ErlangParser.parse(dir+f.getName(), AREA));
					
					if((start.get(Calendar.HOUR_OF_DAY) == 12) && start.get(Calendar.MINUTE) == 00)
						System.out.println(start.getTime());
					
				}
			}
		}
	}
	
	
	public static double[][] computeTemporalAvg(Calendar START, Calendar END, CityArea AREA) throws Exception  {
		double[][] norm = new double[AREA.getNrows()][AREA.getNcols()];
		double size[][]  = new double[norm.length][norm[0].length];
		
		for(String dir: AREA.getBase_in_dirs()) {
			File fdir = new File(dir);
			File[] files = fdir.listFiles();
			for(File f: files) {
				String[] elements = f.getName().split("_");
				Calendar start = ErlangParser.parseDateFileName(elements[3]);
				Calendar end = ErlangParser.parseDateFileName(elements[4]);
			    
				if(!start.before(START) && !end.after(END)) {
					double[][] m = ErlangParser.parse(dir+f.getName(), AREA).getMat();
					
					for(int i=0; i<m.length; i++)
					for(int j=0; j<m[i].length;j++) 
					if(m[i][j]>0) {
						norm[i][j] = norm[i][j] + m[i][j];
						size[i][j] ++;
					}
						
					if((start.get(Calendar.HOUR_OF_DAY) == 12) && start.get(Calendar.MINUTE) == 00)
						System.out.println(start.getTime());
						
				}
			}
		}
		
		for(int i=0; i<norm.length; i++)
		for(int j=0; j<norm[i].length;j++) 
			norm[i][j] = norm[i][j] / size[i][j]; 
		
		return norm;
	}
	
	public String[][] computeSignature(List<MatrixTime> wd, List<MatrixTime> we){
		
		MatrixTime mt = wd.get(0);
		String[][] sign = new String[mt.getMat().length][mt.getMat()[0].length];
		
		for(int i=0; i<mt.getMat().length; i++)
			for(int j=0; j<mt.getMat()[i].length;j++) {
				String s = "";
				double max = computeMax(wd,i,j);
				double min = computeMin(wd,i,j);
				
				//weekdays morning
				if(WDMORNING){
					for(int h=0;h<12;h++)
						s += (wd.get(h).getMat()[i][j]-min)/(max-min)+",";
				}
				
				//weekdays afternoon
				if(WDAFTERNOON){
					for(int h=12;h<24;h++)
						s += (wd.get(h).getMat()[i][j]-min)/(max-min)+",";
				}
				
				max = computeMax(we,i,j);
				min = computeMin(we,i,j);
				
				//weekends morning
				if(WEMORNING){
					for(int h=0;h<12;h++)
						s += (we.get(h).getMat()[i][j]-min)/(max-min)+",";					
				}
				
				//weekends afternoon
				if(WEAFTERNOON){
					for(int h=12;h<24;h++)
						s += (we.get(h).getMat()[i][j]-min)/(max-min)+",";
				}

				sign[i][j] = s.substring(0,s.length()-1);
			}
		
		return sign;		
	}
	
	public double computeMax(List<MatrixTime> mt, int i, int j){
		double max = Double.MIN_VALUE;
		
		for(MatrixTime et: mt)
			if(et.getMat()[i][j] > max)
				max = et.getMat()[i][j];		
		
		return max;
	}
	
	public double computeMin(List<MatrixTime> mt, int i, int j){
		double min = Double.MAX_VALUE;
		
		for(MatrixTime et: mt)
			if(et.getMat()[i][j] < min)
				min = et.getMat()[i][j];		
		
		return min;
	}
	
	public void fuzzyCmeans(String[][] signature, MatrixTime mt){
		XYSeries series = new XYSeries("Number of clusters vs distance");
		
		DataHolder data = new DataHolder("Pixels");
		
		for(int i=0;i<signature.length;i++)
			for(int j=0;j<signature[i].length;j++){
				String[] split = signature[i][j].split(",");
				double[] trend = new double[split.length];
				for(int k=0;k<trend.length;k++)
					trend[k] = Double.parseDouble(split[k]);
				
				data.add(new DataPoint(trend));
			}
		
		System.out.println("Dimension=" + data.getDimention());
		System.out.println("No rows=" + data.getSize());
		
        // check loaded data (print!)
		data.print();
		
		/*
		
		for(double d=0.25;d<=0.45;d+=0.01){
			System.out.println();
			System.out.println("DISTANCE :"+d);
			System.out.println();
			Partition pat = new Partition(data);
			// set No clusters, precision, fuzziness (dummy if non-applicable),max number of iterations
			pat.set(data.getDimention(),d, 1.1831831524262644, 10000);
			// probability for membership (only for Fuzzy algorithm)
			pat.setProbab(0.2);

			//define types of cluster analysis: 132 Fuzzy C-means clustering: Best estimate
			pat.run(132);
			System.out.println("\nalgorithm: " + pat.getName());
			System.out.println("Compactness: " + pat.getCompactness());
			System.out.println("No of final clusters: " + pat.getNclusters());
			int [] p = pat.getPoints();
			for(int i=0;i<p.length;i++)
				System.out.println(i+" "+p[i]);
			DataHolder Centers = pat.getCenters();
			System.out.println("Positions of centers: ");
			Centers.print();
			
			
	        // show cluster association
		    for (int m = 0; m < data.getSize(); m++) {
		    	DataPoint dp = data.getRaw(m);
		    
			    int k = dp.getClusterNumber();
			    if(k == -1){
				    System.out.println("point="+m+" associated with= "+k);
				    dp.showAttributes();
			    }
			    else{
				    DataPoint center = Centers.getRaw(k);
				    double dist = dp.distance(dp, center);
				    System.out.println("point="+m+" associated with= "+k+" distance = "+dist);
				    dp.showAttributes();
			    }
		    }
		    
		    series.add(d,pat.getNclusters());
			
		}
		
		
		XYDataset dataseries = new XYSeriesCollection(series);
		
        JFreeChart chart = ChartFactory.createXYLineChart(
                "XY Series Demo",
                "X", 
                "Y", 
                dataseries,
                PlotOrientation.VERTICAL,
                true,
                true,
                false
            );
		     
        try{
        	ChartUtilities.saveChartAsJPEG(new File("chart.jpg"), chart, 500, 300);
        	
        }catch(Exception e){
        	e.printStackTrace();
        }
        
        */
		
        SetEnv.DATA=data;
        SetEnv.NRow=data.getSize();
        SetEnv.Dim=data.getDimention();  
        Fuzzy fuzzy = new Fuzzy();
        
        
        	
        double iteration = 10;
        double numClusters = 0;
        	
        for(int i=0;i<iteration;i++){
        	//max iterations, epsilon, fuzziness
            fuzzy.setOptions(10000,0.0001,1.1831831524262644);
            fuzzy.runBest();
                
            numClusters += fuzzy.getClusters();
        		
        }
        System.out.println("NUMBER OF CLUSTERS = "+(numClusters/iteration));
        
        //run with the computed number of clusters
        fuzzy.setClusters((int)Math.ceil(numClusters/iteration));
        
        //run with 3 clusters
        //fuzzy.setClusters(3);
        fuzzy.run();
      

        
        System.out.println("# of clusters = "+fuzzy.getClusters()+" compactness = "+fuzzy.getCompactness());
        DataHolder Centers = fuzzy.getCenters();
		System.out.println("Positions of centers: ");
		Centers.print();
        
        //points x # clusters
        double[][] m=fuzzy.Membeship();  
        System.out.println("Print membership probability=");
        VEC.printMatrix(SetEnv.NRow,fuzzy.getClusters(),m,5,2);
        
        
	    for (int l = 0; l < data.getSize(); l++) {
	    	DataPoint dp = data.getRaw(l);
	    	
	    	int index = -1;
	    	double max = Double.MIN_VALUE;
	    	for(int j=0;j<m[l].length;j++)
	    		if(m[l][j] > max && m[l][j]>=PROB_LIMIT){
	    			index = j;
	    			max = m[l][j];
	    	    }
	    	
	    	dp.assignToCluster(index);
	    
		    int k = dp.getClusterNumber();
			System.out.println("point="+l+" associated with= "+k);
		
	    }
		
        
        KMLPrinterCluster.printMap(mt, data, signature, OUT_MAP, COLORS_FILE);
		
	}

}
